package org.jiamandan.com.config;


import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSON;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jiamandan.com.cache.CacheNames;
import org.jiamandan.com.constant.BizConstant;
import org.jiamandan.com.dto.YXBasicDto;
import org.jiamandan.com.dto.ZDBasicDto;
import org.jiamandan.com.strategy.JSYDZContextService;
import org.jiamandan.com.strategy.YxCardContextService;
import org.jiamandan.com.strategy.ZDListenerContextService;
import org.jiamandan.com.utils.CommonUtil;
import org.jiamandan.com.utils.RedisUtil;
import org.jiamandan.com.utils.YxObjectUtil;
import org.jiamandan.com.utils.ZDObjectUtil;

import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;

/**
 * @version Revision 1.0.1
 * @版权：版权所有 (c) 2021
 * @author:Jmd
 * @see:
 * @创建日期：2023-03-16 13:48
 * @功能说明：
 * @begin
 * @修改记录:
 * @修改后版本 修改人        修改内容
 * @2023-03-16 13:48	         Jmd        	创建
 * @end
 */
@Slf4j
public class TcpInboundAdapter extends ChannelInboundHandlerAdapter {

    public static final ChannelGroup clients;
    public static final ZDListenerContextService contextService;
    public static final JSYDZContextService jsyContextService;

    public static final YxCardContextService yxCardContextService;

    public static final RedisUtil redisUtil;

    static {
        clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
        contextService = SpringUtil.getBean(ZDListenerContextService.class);
        yxCardContextService = SpringUtil.getBean(YxCardContextService.class);
        jsyContextService = SpringUtil.getBean(JSYDZContextService.class);
        redisUtil = SpringUtil.getBean(RedisUtil.class);
    }
    /**
     * 从客户端收到新的数据时，这个方法会在收到消息时被调用
     *
     * @param ctx
     * @param msg
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        InetSocketAddress localSocket = (InetSocketAddress) ctx.channel().localAddress();
        int port = localSocket.getPort();
        Channel client = ctx.channel();
//        if (BizConstant.HardSoftFactoryPort.JSY_PORTS.contains(port)) {
        if (BizConstant.HardSoftFactoryPort.JSY_XIN == port) {
            String jsyPayload = (String) msg;
            log.info("收到消息: " + jsyPayload);
            log.info("收到JMD-JSY报文,端口：" + port + ":[" + jsyPayload + "]");
            String[] items = StringUtils.split(jsyPayload, BizConstant.separator);
            String cmd = StringUtils.substring(items[0], 1, items[0].length());
            if ("T00001".equals(cmd)) {
                String deviceNo = items[5];
                if (getChannelId(deviceNo, ctx.channel().id().asShortText()) == null) {
                    updateCacheByDeviceNo(deviceNo, client);
                }
            }
            jsyContextService.execute(cmd, jsyPayload, ctx);
        } else {
            byte[] data = (byte[]) msg;
            // 由于我们配置的是 字节数组 编解码器，所以这里取到的用户发来的数据是 byte数组
            String payload = CommonUtil.bytesToHex(data);
            if (BizConstant.HardSoftFactoryPort.YU_XIN == port) {
                log.info("收到JMD-YX汽车桩报文,端口：18082:【" + payload + "】");
                YXBasicDto dto = YxObjectUtil.parse(payload);
                if (dto.getFrameType().equals("03")) {
//                    String deviceNo = CommonUtil.removeZero(dto.getData().substring(0, 14), false);
                    String deviceNo = dto.getData().substring(0, 14);
                    if (getChannelId(deviceNo, ctx.channel().id().asShortText()) == null) {
                        updateCacheByDeviceNo(deviceNo, client);
                    }
                }
                log.info("收到JMD-YX汽车桩报文,CMD=" + dto.getFrameType() + "报文 : " + payload);
                yxCardContextService.execute(dto, client);
                // 如果是JMD-JSY，走此端口
            }
            // 如果是JMD-YX，则走此入口
            if (BizConstant.HardSoftFactoryPort.YD_XIN == port) {
                log.info("收到JMD-YD报文,端口：18080:[" + payload + "]");
                if (payload.equals("6c696e6b")) {
                    log.error("防中国移动踢掉网的，长度固定为4字节: " + payload);
                    return;
                }
                if (payload.startsWith("38")) {
                    log.error("JMD-YD 这是通信模块每次连上socket时，channel:【" + client.id() + "】都会（第一时间）发送一次sim卡号给socket: " + payload);
                    return;
                }
                ZDBasicDto dto = ZDObjectUtil.parse(payload);
                if (dto == null) {
                    log.error("接收到非法报文 : " + payload);
                    return;
                }
                //获取到设备编号
                String deviceNo = null;
                if (dto.getStartPrefix().equals("68")) {
                    deviceNo = CommonUtil.hexToString(dto.getDeviceNo()).trim();
                } else {
                    deviceNo = StrUtil.toString(Integer.parseInt(CommonUtil.reverseHex(dto.getDeviceNo()).substring(2), 16));
                }
                if (getChannelId(deviceNo, ctx.channel().id().asShortText()) == null) {
                    updateCacheByDeviceNo(deviceNo, client);
                }
                contextService.execute(dto, client);
            }
        }
    }

    /**
     * 从客户端收到新的数据、读取完成时调用
     *
     * @param ctx
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        log.debug("channel Read Complete");
        ctx.flush();
    }

    /**
     * 当出现 Throwable 对象才会被调用，即当 Netty 由于 IO 错误或者处理器在处理事件时抛出的异常时
     *
     * @param ctx
     * @param cause
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.debug("exceptionCaught");
        log.error(cause.getMessage(), cause);
        log.info("链接报错：" + ctx.channel().id().asShortText());
        cause.printStackTrace();
        ctx.channel().close();
        clients.remove(ctx.channel());
        removeCacheByChannelId(ctx.channel().id());
    }

    /**
     * 客户端与服务端第一次建立连接时 执行
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        ctx.channel().read();
        InetSocketAddress inSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIp = inSocket.getAddress().getHostAddress();
        //此处不能使用ctx.close()，否则客户端始终无法与服务端建立连接
        log.debug("channelActive: " + clientIp + ctx.name());
        log.info("新的客户端链接：" + ctx.channel().id().asShortText());
        clients.add(ctx.channel());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        log.info("监听断电离线客户端：{}",ctx.channel().id());
        super.handlerRemoved(ctx);
    }

    /**
     * 客户端与服务端 断连时 执行
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);
        InetSocketAddress inSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIp = inSocket.getAddress().getHostAddress();
        log.debug("channelInactive: " + clientIp);
        log.info("断开链接：" + ctx.channel().id().asShortText());
        clients.remove(ctx.channel());
        removeCacheByChannelId(ctx.channel().id());
        ctx.close(); //断开连接时，必须关闭，否则造成资源浪费，并发量很大情况下可能造成宕机
    }

    /**
     * 服务端当read超时, 会调用这个方法
     *
     * @param ctx
     * @param evt
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        super.userEventTriggered(ctx, evt);
        InetSocketAddress inSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIp = inSocket.getAddress().getHostAddress();
        ctx.close();//超时时断开连接
        log.debug("userEventTriggered: " + clientIp);
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) {
        log.debug("channelRegistered");
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) {
        log.debug("channelUnregistered");
    }

    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) {
        log.debug("channelWritabilityChanged");
    }


    /**
     * 获取channelId
     *
     * @param deviceNo
     * @return
     */
    private ChannelId getChannelId(String deviceNo, String oldChannelId) {
        Map<String, ChannelId> result =(Map<String, ChannelId> ) redisUtil.get(CacheNames.TCP_CHANNEL);
        if (result == null || result.get(deviceNo) == null) {
            return null;
        }
        if(result.get(deviceNo) instanceof ChannelId){
            ChannelId channelId =  result.get(deviceNo);
            String shortText = channelId.asShortText();
            log.info("当前缓存channelId：【" + channelId + "】接收消息的channelId:【" + oldChannelId + "】");
            if (!oldChannelId.equals(channelId.asShortText())) {
                log.info("移除旧连接:" + channelId);
                clients.remove(channelId);
                this.removeCacheByChannelId(channelId);
                return null;
            }
            log.info("判断当前缓存channelId【" + channelId + "】是否为空：" + clients.find(channelId));
            if (clients.find(channelId) != null) {
                return channelId;
            } else {
                removeCacheByChannelId(channelId);
                return null;
            }
        }
        return null;
    }

    /**
     * @param deviceNo
     * @param client
     */
    private void updateCacheByDeviceNo(String deviceNo, Channel client) {
        ChannelId channelId = client.id();
        Map<String, ChannelId> result = (Map<String, ChannelId> ) redisUtil.get(CacheNames.TCP_CHANNEL);
        if (result == null) {
            result = new HashMap<>();
        }
        result.put(deviceNo, channelId);
        log.info("======" + deviceNo + "====" + channelId + "===============");
        log.info(result.toString());
        if (clients.find(channelId) != null) {
            log.info(deviceNo + "添加到client中"+ JSON.toJSON(result));
            clients.add(client);
            redisUtil.set(CacheNames.TCP_CHANNEL,result);
        }
    }

    /**
     * @param channelId
     */
    private void removeCacheByChannelId(ChannelId channelId) {
        Map<String, ChannelId> result = (Map<String, ChannelId> ) redisUtil.get(CacheNames.TCP_CHANNEL);
        if (result == null) {
            return;
        }
        for (Map.Entry<String, ChannelId> entry : result.entrySet()) {
           if(entry.getValue() instanceof ChannelId){
                if (entry.getValue().equals(channelId.asShortText())) {
                    result.remove(entry.getKey());
                    break;
                }
            }
        }
    }
}
